#引入隐含层,隐含层越多,神经网络就具有了一些特殊的属性,比如引入非线性的隐含层后,理论上只要隐含节点足够多，
#即使只有一个隐含层的神经网络也可以拟合任意函数。
#同时隐含层越多,越容易拟合复杂函数。理论研究表明,为了拟合复杂函数需要的隐含节点的数目,基本上随着隐含层数量增多呈现
#指数下隆。
#深度学习的特点：层数越深，概念越抽象。但层数较的神经网络会遇到许多困难，比如容易过拟合、参数难以调试、梯度弥散
#常用的解决办法：Dropout、Adagrad、


#1、过拟合(指模型ReLU预测准确率在训练集上升高，但是在测试集上反而下降，这通常意味着泛化性不好，Hinton教授团队提o出了一个思路简单但是非常的效的方法，
#   Dropout,思路：在训练时，将神经网络某一层的输出节点数据随机丢弃一部分。这种做法实质上等于创造出了很多新的随机样本，通过增大样本量、减少特征数量
#   来防止过拟合
#   Dropout其实也算是一种bagging方法，我们可以理解成每次丢弃节点数据是对特征的一种采样，相当于我们训练了一个ensemble的神经网络模型，对每个样本都做
#   特征采样，只不过没有训练多个神经网络模型，只有一个融合的神经网络
#2、参数难以调试：尤其是SGD的参数，对SGD设置不同的学习速率，最后得到的结果可能差巨大。神经网络通常不是一个凸优化的问题，它处处充满了局部最优。
#   因此就有像Adagrad、Adam、Adadelta 等自适应的方法可以减轻调试参数的负担。对于这些优化算法，通常我们使用它默认的参数设置就可以取得一个比较好的效果。
#   而SGD则需要对学习速率、Momentum、Nesteroy等参数进行比较复杂的调试，当调试的参数较为合适问题时，才能达到比较好的效果。
#3、梯度弥散，在ReLU激活函数出现之前，神经网络训练全部都是用Sigmoid作为激活函数，在此之前是线性激活函数、阶梯激活函数，Sigmoid函数在反向传播中梯度
#   值会逐渐减小，经过多层的传递后会呈现指数级急剧减小，因此梯度值在传递到前面几层时就变得非常小了。
#   ReLU是一个简单的非线性函数y=max(0,x)，它在坐标轴是一条折数，当x<= 0 时，y=0；当x>0时，y=x。非常类似于人脑的阈值响应机制。目前，ReLU及其变种(EIU,
#   PReLU,RReLU）已经成为了最主流的激活函数，实践中大部分情况下（包括MLP和CNN，RNN内部主要还是使用Sigmoid、Tanh、Hard Sigmoid）将隐含层的激活函数
#   替换为ReLU都可以带来训练速度及模型准确率的提升。（
#4、当然神经网络的输出层一般还是Sigmoid函数，因为它最接近概率输出分布

#神经网络的隐含层的一个代表性的功能是可以解决XOR问题。

from tensorflow.examples.tutorials.mnist import  input_data
import tensorflow as tf
mnist = input_data.read_data_sets("/",one_hot=True)

sess = tf.InteractiveSession()

#给隐含层进行参数设置Variale并进行初始化
#in_units是输入节点数，h1_units即隐含层的输出节点数设为300(在此模型中隐含节点数设在200~1000范围内的结果区别都不大）
#w1、b1是隐含层的权重和偏置,我们将偏置全部赋值为0，并将权重初始化为截断的正态分布，其标准差0.1，这一步可以通过tf.truncated_normal
#实现，
# 注意：同时因为模型使用激活函数是ReLU，所以需要使用正态分布给偏置赋上一些小的非零值来避免0梯度，
#       在其它一些模型中，有时需要给偏置赋上一些小的非零值来避免dead neuron(死亡神经元）
#而对最后输出层的softmax ,直接将权重w2和偏置b2全部初始化为0即可，（上面提到过Sigmoid在0附近最敏感、梯度最大）

in_units = 784
h1_units = 300
W1 = tf.Variable(tf.truncated_normal([in_units,h1_units],stddev=0.1))
b1 = tf.Variable(tf.zeros([h1_units]))
W2 = tf.Variable(tf.zeros([h1_units,10]))
b2 = tf.Variable(tf.zeros([10]))

#定义输入x的placeholder.另外因为在训练和预测时，Dropout的比率keep_prob（即保留节点的概率）是不一样的，通常训练时小于1，
#而预测时则等于1，所以也把Dropout的比率作为计算图的输入，并定义成一个placeholder
x = tf.placeholder(tf.float32, [None,in_units])
keep_prob = tf.placeholder(tf.float32)

#下面定义模型结构,
#第一步： 定义算法公式
hidden1 = tf.nn.relu(tf.matmul(x,W1) + b1)
hidden1_drop = tf.nn.dropout(hidden1, keep_prob)
y = tf.nn.softmax(tf.matmul(hidden1_drop,W2) + b2)

#第二步:定义损失函数和选择优化器来优化loss,这里的损失函数继续使用交叉信息熵，和之前一至，但是优化器选择自适应优化器Adagrad
#并把学习速率设为0.3，这里我们直接使用tf.train.AdagradOptimizer就可以了（其它的还有Adadelate,Adam等）

y_ = tf.placeholder(tf.float32, [None, 10])

cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))

train_step = tf.train.AdagradOptimizer(0.3).minimize(cross_entropy)

#第三步，训练模型

tf.global_variables_initializer().run()
for i in range(3000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    train_step.run({x: batch_xs, y_: batch_ys, keep_prob: 0.75})

#第四步：准确率评测，这里加入一个keep_prob作为输入，因为是预测部分，所以我们直接令Keep_prob等于1,

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

